package com.yeskery.nut.util;

import com.yeskery.nut.bean.*;
import com.yeskery.nut.core.NutException;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 反射工具类
 * @author sprout
 * 2022-06-15 15:27
 */
public class ReflectUtils {

    /**
     * 私有化构造方法
     */
    private ReflectUtils() {
    }

    /**
     * 获取目标对象
     * @param clazz 目标对象类型
     * @return 目标对象
     * @param <T> 目标对象类型
     */
    public static <T> T getNoConstructorTarget(Class<T> clazz) {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            throw new NutException("Class[" + clazz.getName() + "] Instance Create Fail.", e);
        }
    }

    /**
     * 获取目标对象
     * @param targetMap 目标对象map
     * @param clazz 目标对象类型
     * @return 目标对象
     */
    public static Object getNoConstructorTarget(Map<Class<?>, Object> targetMap, Class<?> clazz) {
        Object target = targetMap.get(clazz);
        if (target == null) {
            target = getNoConstructorTarget(clazz);
            targetMap.putIfAbsent(clazz, target);
        }
        return target;
    }

    /**
     * 获取目标对象
     * @param applicationContext 应用上下文
     * @param targetMap 目标对象map
     * @param clazz 目标对象类型
     * @return 目标对象
     */
    public static Object getTargetByCache(ApplicationContext applicationContext, Map<Class<?>, Object> targetMap, Class<?> clazz) {
        Object value = targetMap.get(clazz);
        if (value != null) {
            return value;
        }
        BaseApplicationContext baseApplicationContext = (BaseApplicationContext) applicationContext;
        Constructor<?> constructor = getBeanFitConstructor(clazz);
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setConstructor(constructor);
        try {
            Object target = baseApplicationContext.createBeanInstance(beanDefinition);
            if (target == null) {
                throw new NutException("Class[" + clazz.getName() + "] Instance Create Fail.");
            }
            baseApplicationContext.beanAttributePadding(target);
            targetMap.putIfAbsent(clazz, target);
            return target;
        } catch (Exception e) {
            throw new NutException("Class[" + clazz.getName() + "] Instance Create Fail.", e);
        }
    }

    /**
     * 获取目标对象，并且将当前bean注册到应用上下文
     * @param applicationContext 应用上下文
     * @param clazz 目标对象类型
     * @return 目标对象
     */
    public static Object getTargetByApplication(ApplicationContext applicationContext, Class<?> clazz) {
        try {
            return applicationContext.getBean(clazz);
        } catch (NoSuchBeanException e) {
            // Not Need Deal.
        }
        try {
            AnnotationApplicationContext annotationApplicationContext = (AnnotationApplicationContext) applicationContext;
            annotationApplicationContext.registerBean(clazz);
            return applicationContext.getBean(clazz);
        } catch (Exception e) {
            throw new NutException("Class[" + clazz.getName() + "] Instance Create Fail.", e);
        }
    }

    /**
     * 获取bean构造器
     * @param beanClass bean类型
     * @return bean构造器
     */
    public static Constructor<?> getBeanFitConstructor(Class<?> beanClass) {
        Optional<Constructor<?>> constructorOptional = Arrays.stream(beanClass.getConstructors())
                .min(Comparator.comparing(Constructor::getParameterCount));
        if (!constructorOptional.isPresent()) {
            throw new BeanException("Can Not Found Bean [" + beanClass.getName() + "] Constructor");
        }
        return constructorOptional.get();
    }

    /**
     * 获取默认的bean名称
     * @param beanClass bean类型
     * @return 默认的bean名称
     */
    public static String getDefaultBeanName(Class<?> beanClass) {
        return getDefaultBeanName(beanClass.getSimpleName());
    }

    /**
     * 获取默认的bean名称
     * @param className bean类型名称
     * @return 默认的bean名称
     */
    public static String getDefaultBeanName(String className) {
        if (StringUtils.isEmpty(className)) {
            return className;
        } else if (className.length() == 1) {
            return className.toLowerCase();
        } else {
            return String.valueOf(className.charAt(0)).toLowerCase() + className.substring(1);
        }
    }

    /**
     * 获取bean所有字段
     * @param beanClass bean类型
     * @return bean所有字段
     */
    public static Field[] getBeanAllField(Class<?> beanClass) {
        List<Field> fields = new LinkedList<>();
        Class<?> currentClass = beanClass;
        do {
            Field[] declaredFields = currentClass.getDeclaredFields();
            fields.addAll(Arrays.asList(declaredFields));
            currentClass = currentClass.getSuperclass();
        } while (currentClass != null);

        return fields.toArray(new Field[0]);
    }

    /**
     * 获取bean依赖的指定注解类的字段
     * @param beanClass bean类型
     * @param annotationClass 指定注解类型
     * @return bean依赖的指定注解类的字段
     */
    public static Field[] getBeanAnnotationField(Class<?> beanClass, Class<? extends Annotation> annotationClass) {
        List<Field> fields = new LinkedList<>();
        Class<?> currentClass = beanClass;
        do {
            Field[] declaredFields = currentClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.isAnnotationPresent(annotationClass)) {
                    fields.add(declaredField);
                }
            }
            currentClass = currentClass.getSuperclass();
        } while (currentClass != null);

        return fields.toArray(new Field[0]);
    }

    /**
     * 获取Bean的声明方法
     * @param clazz 配置类对象
     * @param annotationClass 注解类型
     * @return Bean的声明方法
     */
    public static Method[] getBeanAnnotationMethod(Class<?> clazz, Class<? extends Annotation> annotationClass) {
        List<Method> methods = new LinkedList<>();
        Class<?> currentClass = clazz;
        do {
            Method[] declaredMethods = currentClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (declaredMethod.isAnnotationPresent(annotationClass)) {
                    methods.add(declaredMethod);
                }
            }
            currentClass = currentClass.getSuperclass();
        } while (currentClass != null);

        return methods.toArray(new Method[0]);
    }

    /**
     * 获取Bean所有的声明方法
     * @param clazz 配置类对象
     * @return Bean的声明方法
     */
    public static Method[] getBeanMethods(Class<?> clazz) {
        List<Method> methods = new LinkedList<>();
        Class<?> currentClass = clazz;
        do {
            Method[] declaredMethods = currentClass.getDeclaredMethods();
            methods.addAll(Arrays.asList(declaredMethods));
            currentClass = currentClass.getSuperclass();
        } while (currentClass != null);
        return methods.toArray(new Method[0]);
    }

    /**
     * 设置对象字段值
     * @param object 对象
     * @param field 字段
     * @param value 字段值
     * @throws Exception 异常
     */
    public static void setObjectFieldValue(Object object, Field field, Object value) throws Exception {
        if (value != null) {
            boolean accessible = field.isAccessible();
            if (!accessible) {
                field.setAccessible(true);
            }
            field.set(object, value);
            if (!accessible) {
                field.setAccessible(false);
            }
        }
    }

    /**
     * 获取对象字段值
     * @param object 对象
     * @param field 字段
     * @return 字段值
     * @throws Exception 异常
     */
    public static Object getObjectFieldValue(Object object, Field field) throws Exception {
        boolean accessible = field.isAccessible();
        if (!accessible) {
            field.setAccessible(true);
        }
        Object result = field.get(object);
        if (!accessible) {
            field.setAccessible(false);
        }
        return result;
    }

    /**
     * 获取指定名称的方法
     * @param clazz 类对象
     * @param methodName 方法名称
     * @param parameterTypes 参数数组
     * @param returnType 返回对象
     * @return 指定的方法
     */
    public static Method findSpecifiedMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Class<?> returnType) {
        if (StringUtils.isEmpty(methodName)) {
            throw new NutException("Method Name Can Not Be Empty.");
        }
        for (Method method : clazz.getMethods()) {
            if (methodName.equals(method.getName()) && isMatchMethod(method, parameterTypes, returnType)) {
                return method;
            }
        }
        return null;
    }

    /**
     * 获取指定注解的方法
     * @param clazz 类对象
     * @param annotationType 方法名称
     * @param parameterTypes 参数数组
     * @param returnType 返回对象
     * @return 指定的方法集合
     */
    public static Set<Method> findSpecifiedMethod(Class<?> clazz, Class<? extends Annotation> annotationType,
                                             Class<?>[] parameterTypes, Class<?> returnType) {
        Set<Method> methods = new HashSet<>();
        for (Method method : clazz.getMethods()) {
            if (method.isAnnotationPresent(annotationType) && isMatchMethod(method, parameterTypes, returnType)) {
                methods.add(method);
            }
        }
        return methods;
    }

    /**
     * 判断指定名称的方法是否存在
     * @param clazz 类对象
     * @param methodName 方法名称
     * @param parameterTypes 方法参数类型数组
     * @return 指定的方法
     */
    public static boolean existMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
        if (StringUtils.isEmpty(methodName)) {
            throw new NutException("Method Name Can Not Be Empty.");
        }
        try {
            clazz.getMethod(methodName, parameterTypes == null ? new Class[0] : parameterTypes);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    /**
     * 获取方法的唯一名称
     * @param method 方法对象
     * @return 方法的唯一名称
     */
    public static String getMethodUniqueName(Method method) {
        return method.getReturnType().getName() + ":" + method.getName() + ":"
                + Arrays.stream(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(","));
    }

    /**
     * 复制对象字段
     * @param source 原对象
     * @param target 目标对象
     */
    public static void copyFields(Object source, Object target) {
        Field[] targetFields = getBeanAllField(target.getClass());
        for (Field field : getBeanAllField(source.getClass())) {
            Arrays.stream(targetFields)
                    .filter(f -> !Modifier.isStatic(f.getModifiers()))
                    .filter(f -> f.getName().equals(field.getName()))
                    .filter(f -> f.getType().equals(field.getType()))
                    .findFirst()
                    .ifPresent(f -> {
                        try {
                            setObjectFieldValue(target, f, getObjectFieldValue(source, field));
                        } catch (Exception e) {
                            throw new NutException("Object Copy Failure.", e);
                        }
                    });
        }
    }

    /**
     * 调用其他对象的指定方法
     * @param object 要调用的对象
     * @param method 要调用的方法
     * @param args 方法参数数组
     * @return 方法执行结果
     */
    public static Object invokeSpecifiedMethod(Object object, Method method, Object[] args) {
        try {
            Method specifiedMethod = object.getClass().getMethod(method.getName(), method.getParameterTypes());
            return specifiedMethod.invoke(object, args);
        } catch (Exception e) {
            throw new NutException("Method Invoke Fail.", e);
        }
    }

    /**
     * 获取实际类型参数类型
     * @param type 类型
     * @param index 位置索引
     * @return 实际类型参数类型
     */
    public static Class<?> getActualTypeArgumentType(Type type, int index) {
        ParameterizedType parameterizedType = null;
        if (type instanceof ParameterizedType) {
            parameterizedType = (ParameterizedType) type;
        }
        if (parameterizedType == null) {
            return (Class<?>) type;
        } else {
            return (Class<?>) parameterizedType.getActualTypeArguments()[index];
        }
    }

    /**
     * 创建集合类型
     * @param clazz 类型
     * @param initSize 初始化大小
     * @return 集合类型
     */
    public static Collection<Object> createTypeCollection(Class<?> clazz, Integer initSize) {
        Collection<Object> collection;
        if (List.class.isAssignableFrom(clazz)) {
            collection = initSize == null ? new ArrayList<>() : initSize == 0 ? Collections.emptyList() : new ArrayList<>(initSize);
        } else {
            collection = initSize == null ? new HashSet<>() : initSize == 0 ? Collections.emptySet() : new HashSet<>(initSize);
        }
        return collection;
    }

    /**
     * 创建集合类型
     * @param field 字段
     * @param initSize 初始化大小
     * @return 集合类型
     */
    public static Collection<Object> createFieldTypeCollection(Field field, Integer initSize) {
        return createTypeCollection(field.getType(), initSize);
    }

    /**
     * 是否是匹配的方法
     * @param method 发放对象
     * @param parameterTypes 参数类型数组
     * @param returnType 方法返回类型
     * @return 是否是匹配的方法
     */
    private static boolean isMatchMethod(Method method, Class<?>[] parameterTypes, Class<?> returnType) {
        Class<?>[] parameterClasses = method.getParameterTypes();
        if (parameterClasses.length != parameterTypes.length) {
            return false;
        }
        for (int i = 0; i < parameterClasses.length; i++) {
            if (!parameterClasses[i].equals(parameterTypes[i])) {
                return false;
            }
        }
        return returnType.equals(method.getReturnType());
    }
}
